Coding Style
A few years ago, I searched for resources regarding C# Coding Style. However, as time passed, these resources often became difficult to find—either because the relevant blogs were taken down or because Microsoft had revised their versions, leaving some less common definitions uncertain. Therefore, I began recording my notes in this article for my own easy reference.
Microsoft provides a package for code style analysis; you can refer to the rules on GitHub at StyleCopAnalyzers. However, when I write code, I only follow these rules loosely, still adhering primarily to my early programming habits or rules I encountered previously. For example, while I might place left braces on a new line in other articles, I actually place them on the same line when writing code. This is because my early PHP formatting rules were based on Java, and Java places left braces on the same line.
WARNING
The coding style defined in this article is based on my personal preferences and does not necessarily represent the "best" way. It is best to follow your team's definitions or your own established habits.
Naming Rules
For naming, I follow C# casing conventions entirely. You can generally refer to the following two articles:
- StyleCopAnalyzers: I have summarized them as follows:
- Everything is Pascal Case, except for variables (Camel Case) and fields (depending on the situation).
- Fields are Camel Case, except for the following cases:
- Public and Internal fields use Pascal Case because they provide external access.
- Constants and Static Readonly fields use Pascal Case because they represent constants.
- Fields do not require prefixes like "", "m", or "s_".
- Interfaces must start with "I".
- For generic type parameters, if there is only one and it can be any type, use "T". If there are multiple, or if there are specific type requirements, use words starting with "T", such as
List<T>andDictionary<TKey, TValue>.
- Casing Conventions for Acronyms:
There are two types of abbreviations:
- Acronyms: Formed from the first letters of several words or phrases. For example, SHIELD is an acronym for Strategic Homeland Intervention, Enforcement and Logistics Division.
- Abbreviations: Formed by taking a few letters from a word.
- The casing of the first letter of an abbreviation depends on whether Pascal Case or Camel Case is used; subsequent letters are handled as follows:
- For three or more letters, regardless of whether it is an acronym or abbreviation, the second letter onwards is lowercase, e.g., "Sql" or "sql".
- For two-letter acronyms, they are all uppercase or all lowercase, e.g., "IO" or "io".
- For two-letter abbreviations, the second letter onwards is lowercase, e.g., "Id" or "id".
You can also refer to the article by Darkthread: "The Confusion of Acronym Casing: LINQHelper or LinqHelper?" (in Traditional Chinese).
WARNING
Although later versions of MSDN removed the casing rules for abbreviations, they still follow these rules during development. It is worth noting that these abbreviation rules only apply to C#. In reality, many languages define abbreviations differently. For example, in the JavaScript DOM, acronyms are always all uppercase or all lowercase, such as innerHTML.
Ordering Rules
StyleCopAnalyzers rules starting with SA12 are related to ordering. Except for methods, I generally follow these rules.
- SA1201: Member ordering.
- SA1202: Access modifier ordering:
- public
- internal
- protected internal
- protected
- private protected
- private
- SA1203: Constant fields must appear before non-constant fields.
- SA1204: Static members must appear above non-static members of the same type.
- SA1206, SA1207: Declaration keyword ordering:
- Access Modifiers 1. protected 2. internal
- static
- Other Keywords
- SA1208, SA1210: When using namespaces, those starting with
System.should be placed first, and the rest should be sorted alphabetically. - SA1208, SA1210, SA1209, SA1211, SA1216, SA1217: Using ordering rules are as follows:
- Using Namespaces: Namespaces starting with
System.are placed first, followed by others in alphabetical order. - Using Static: Sorted by the full type name.
- Using Alias: Sorted by the alias name.
- Using Namespaces: Namespaces starting with
- Aliases must be placed after
usingnamespaces, and aliases are sorted alphabetically. - SA1212: Property and Indexer getters must be placed before setters.
- SA1213: Event add accessors must be placed before remove accessors.
- SA1214: Readonly fields must be placed before non-readonly fields.
Complete Example
using Namespace;
using static Namespace.StaticClassName;
using Namespace = Alias;
public class ClassName {
// I only list these because I don't design Protected Fields or Public Non-static (Const) Fields
#region Fields
public const int ConstantName = 0;
internal const int InternalConstantName = 0;
private const int PrivateConstantName = 0;
public readonly static int ReadonlyStaticFieldName = 0;
private static int StaticFieldName = 0;
private int fieldName = 0;
#endregion
#region Constructors
static ClassName() { }
public ClassName() { }
protected ClassName() { }
private ClassName() { }
#endregion
~ClassName() { }
// I only design Public Delegates
public delegate int Delegate(int x);
// I only design Public Events
public event EventHandler Event;
// I don't design private Properties
#region Properties
public int PropertyName { get; set; }
internal int InternalPropertyName { get; set; }
protected internal int ProtectedInternalPropertyName { get; set; }
protected int ProtectedPropertyName { get; set; }
#endregion
// I don't design private Indexers
#region Indexers
public int this[byte i] { get; set; }
internal int this[short i] { get; set; }
protected internal int this[int i] { get; set; }
protected int this[long i] { get; set; }
#endregion
// The following is the ordering under normal, unrelated conditions
#region Methods
public static void StaticMethodName() { }
internal static void InternalStaticMethodName() { }
public void MethodName() { }
internal void InternalMethodName() { }
protected internal void ProtectedInternalMethodName() { }
protected void ProtectedMethodName() { }
private void PrivateMethodName() { }
#endregion
public static bool operator ==(ClassName left, ClassName right) {
return left == right;
}
}TIP
The code above uses
regionfor readability in this sample. In practice, I do not useregionto categorize code; I only use it to hide details I don't think developers need to see, allowing them to focus on the code that matters.For method ordering, I do not follow the SA1202 (Access Modifiers) rule. Instead, I follow a rule I saw earlier: grouping homogeneous methods together. This way, when I see a method calling another, I can simply scroll down to see the implementation. Example:
Ordering for methods called only once.
csharppublic void Method() { MethodA(); MethodB(); } private void MethodA() { MethodA1(); } private void MethodA1() { } private void MethodB() { }Ordering for methods called repeatedly: Private methods are placed below the first calling method. If multiple similar methods call it, consider placing it below the last calling method.
csharppublic void MethodA() { SubMethod(); } private void SubMethod() { } public void MethodB() { SubMethod(); } public void MethodC() { SubMethod(); } // OR public void MethodA1() { SubMethod1(); } public void MethodB1() { SubMethod1(); } public void MethodC1() { SubMethod1(); } private void SubMethod1() { }
I do not order private methods according to SA1204 (Static and Non-static) because, as mentioned above, I group related methods together. Whether a private method is marked
staticlargely depends on whether it uses instance members. It might have started as a non-static method because it used other private methods, and then became static after refactoring.
Comments
Single-line Comments
- Format: Start with
//followed by a space, e.g.,// Your Comment.
TIP
Although this comment format is one I encountered when I first started programming, most modern guidelines do not strictly require it. However, within Microsoft, they still use this format, as seen in their source code or MSDN examples.
- Position: Can appear above or to the right of the code.
- Purpose: As a proponent of Clean Code, I prefer using comments to explain why something is done, rather than documenting what the code is doing.
Documentation Comments
When using Visual Studio, typing three / on a class, struct, interface, or member generates XML-structured documentation comments. These comments contain specific XML tags used to explain code and provide API documentation. For more details, refer to Documentation Comments.
If you need to tag generic types, you can use {} instead of <> because < and > might be misinterpreted in an XML structure. Example:
/// <seealso cref="Dictionary{TKey, TValue}"/>Private members do not require documentation comments because they are not part of the API and are not exposed to other programs.
Task List
The compiler usually processes special comment keywords. Visual Studio provides "HACK", "TODO", "UNDONE", and "UnresolvedMergeConflict" by default. Comments containing these keywords appear in the Visual Studio Task List window to remind developers of pending tasks.
- TODO: Used to mark features or items that need to be completed.
- UNDONE: Used to mark features or tasks that are in progress but not yet finished.
- HACK: Used to mark code blocks that need modification or correction. Usually a temporary solution added to solve a problem quickly. Once the problem is resolved, this code must be updated and the keyword removed.
- UnresolvedMergeConflict: Related to version control conflicts; usually handled automatically by the version control system.
TIP
The meanings of "TODO" and "UNDONE" differ slightly: "TODO" usually indicates work to be done in the future, while "UNDONE" usually indicates work currently in progress but not yet completed.
Formatting
Left Braces
As mentioned, when I started with PHP, I preferred placing braces on the same line, so I find it unnatural to place them on a new line in C#. Keywords like else, catch, and finally are placed on the same line. Example:
if () {
} else if () {
} else {
}
try {
} catch {
} finally {
}Spacing
The spacing I use in code is generally the same as Visual Studio's defaults. I insert spaces in the following situations:
- After a
,, unless the,is at the end of a line. - After keywords in control flow statements.
- Before and after the colon for base classes or interfaces in a class declaration.
- After the
;inforstatements. - Before and after operators, except for
++and--, where no space is used between the operator and the variable.
Example:
for (int i = 0; i < 10; i++) {
}
Math.Max(1, 2);Operator Line Breaks
When writing essays, it is generally recommended not to start a line with punctuation. Similarly, binary operators in mathematical expressions are easier to read when placed at the end of a line. This was my initial style, but I later saw arguments that placing binary operators at the beginning of a new line improves readability. According to ChatGPT, this style is called "out-of-line style," though I can no longer find the relevant articles. Notably, some programming languages have similar specifications, such as the Google Java Style Guide.
Overall, my formatting style aligns assignment operators (=) and binary operators with the text: assignment operators do not break at the start of a new line, while binary operators do. However, when a Lambda arrow is at a line break, I move it to the new line (unlike point 5 in the Google guide). This is partly because the code I've seen follows this, and partly to distinguish it from =, making it easier to tell if it's a field or a Lambda-style property.
Example:
class Test {
// "=" stays at the end of the line, does not move to the new line
private string field =
"Field";
// "=>" moves to the new line for better distinction
public string Property
=> "Property";
public void Method() {
// "||" moves to the new line
if (condition1
|| condition1
) {
}
// Binary operators move to the new line
int a = b
+ c;
}
}TIP
Note that this is just a demonstration; in practice, the code length and complexity in the example do not require line breaks.
Ternary Operators
For short, non-nested ternary operators, keep them on a single line:
bool result = condition ? result1 : result2;If they are too long, use line breaks in one of the following ways:
bool result = condition1
? result1 : result2;
bool result = condition1
? result1
: result2;For nested ternary operators, always use line breaks:
// Try to place sub-conditions in the ":" part to improve readability
bool result = condition1
? result1
: condition2
? result2
: condition3
? result3
: result4;
// Another nested ternary operator style seen in ASP.NET Core source code
bool result = condition1 ? result1
: condition2 ? result2
: condition3 ? result3
: result4;The two nested ternary operator styles written as if-else:
if (condition1) {
result = result1;
} else {
if (condition2) {
result = result2;
} else {
if (condition3) {
result = result3;
} else {
result = result4;
}
}
}
if (condition1) {
result = result1;
} else if (condition2) {
result = result2;
} else if (condition3) {
result = result3;
} else {
result = result4;
}TIP
As an aside, some languages like PHP provide an elseif keyword, but C#'s else if is simply a result of formatting.
Indentation
I am a "spaces" person. I use 4 spaces for code and 2 spaces for XML.
Empty Lines
- No empty lines between fields; add one empty line between other members to improve readability and allow Visual Studio to display separator lines.
- No empty lines at the beginning or end of blocks.
- At most one empty line at a time.
- Leave one empty line at the end of the file.
Example:
namespace namespace1 {
// No empty line between namespace and class
public class Class1 {
// No empty line between class and field
private string field;
public string Property1 { get; set; }
public string Property2 { get; set; }
public void Method() {
}
}
}
// Add one empty line at the end of the codeTIP
In Unix/Linux environments, files end with a newline character (LF). If an editor opens a file without a trailing newline, it may treat the last line as incomplete, which can cause issues in some editors or version control systems. Therefore, Visual Studio's default formatting and many coding standards add an empty line at the end of the file.
Code Length
I set 80, 100, and 120 character separator lines in Visual Studio to help me evaluate whether code needs to be broken for readability.
TIP
Historically, code length was limited to 80 characters due to terminal and editor constraints. With modern screens, this is no longer a hard limit. However, I still avoid writing excessively long lines to maintain readability and avoid constant horizontal scrolling, especially when developing on smaller screens like laptops.
Change Log
- 2022-11-06 Initial version created.
